#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>

#include <sys/types.h>
#include <sys/socket.h>

#include <unistd.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <iomanip>
#include <openssl/md5.h>

//#include "paquetes.h"
#include "funciones.h"
#include <sys/select.h>
#include <sys/time.h>

int main(int argc, char **argv)
{
	/* control del tiempo */
	struct timeval tv;
	fd_set readfds;

	tv.tv_sec = 0;
	tv.tv_usec = 10000000;
	
	
	/* fin control del tiempo */
	
	int estado = 0;
	
	std::string funcion= argv[1];
	PaqueteVariable paquete;
	md5(funcion,paquete.checksum);
	paquete.largo=funcion.size();
	paquete.checksum[33]='\0';
	paquete.numeroMagico = NUMERO_MAGICO;
	bool confirmacion = 0;
	
	
// 	std::cout << paquete.checksum << std::endl;
	//paquete.letra="ca";
	struct sockaddr_in server;

	socklen_t fromlen = sizeof(struct sockaddr_in);
	int sock = socket(PF_INET, SOCK_DGRAM, 0);
	

//   std::cout << "Se obtuvo el socket " << sock << std::endl;
   
	server.sin_family = AF_INET;
	server.sin_port = htons(5000);
	server.sin_addr.s_addr = inet_addr("190.162.61.92");
	//server.sin_addr.s_addr = inet_addr("192.168.2.4");
	memset(server.sin_zero, '\0', sizeof(server.sin_zero));
	
	//std::cout << serializarString(5,"sumar,2,4")[1]<< std::endl;

	
 	byte * respuesta;
	char * checksum = new char[33];
	connect(sock, (struct sockaddr *) &server, sizeof(server));
	int n = 0;
	while(1)
	{
		switch(estado)
		{
			case 0:
				
				/**
				 * se intenta enviar el PaqueteVariable, el cual contiene la identificacion del paquete de datos
				 * 
				 */

					n = send(sock,&paquete,sizeof(struct PaqueteVariable),0);
					
					if (n < 0)
					{
						perror("PKT NO ENVIADO");
					}else
					{
						estado = 1;
						std::cout << "PKT ENVIADO " << std::endl;
					}
	
			break;
			
			case 1:
				/**
				 * Se espera el acuso desde el servidor
				 * 
				 */

				//falta configurar el tiempo de espera para la recepcion del mensaje

					n = recvfrom(sock,&confirmacion,sizeof(confirmacion),0,(struct sockaddr *)&server,&fromlen);

					if(n <0 )
					{
						perror("NO HAY RECIBO");

					}else
					{
						if(confirmacion == 1)
						{
							std::cout << "ACUSO RECIBIDO OK" << std::endl;
							
							/**
							 * 
							 * ENVIO DEL DATO
							 * 
							 */
							n = send(sock,serializarString(funcion.c_str()),paquete.largo,0);
							if (n < 0)
							{
								perror("DATOS NO ENVIADOS");
								
							}else
							{
								std::cout << "DATOS ENVIADOS" << std::endl;
								estado = 3;
							}
						}else if(confirmacion == 0)
						{
							std::cout << "ACUSO NO RECIBIDO FAIL" << std::endl;
							estado = 0;
						}
						
					}
			break;
			
			
			case  3:
				
				/**
				 *SE ESPERA EL ACUSO DEL DATO 
				 * 
				 */
					n = recvfrom(sock,&confirmacion,sizeof(confirmacion),0,(struct sockaddr *)&server,&fromlen);
					if(n <0 )
					{
						perror("NO HAY RECIBO");
						estado = 0;
					}else
					{
						
						if(confirmacion == 1)
						{
							std::cout << "ACUSO RECIBIDO OK" << std::endl;
							estado = 4;
						}else if(confirmacion == 0)
						{
							std::cout << "ACUSO RECIBIDO FAIL" << std::endl;
							estado = 0;
						}
						
					}
			break;
			case 4:
					/**
					 * 
					 * ESPERA POR PKT DE CON CARACTERISTICAS DE LA RESPUESTA
					 * 
					 */
					std::cout << "ESPERANDO RESPUESTA" << std::endl;
					

					n = recvfrom(sock,&paquete,sizeof(struct PaqueteVariable),0,(struct sockaddr *)&server,&fromlen);
				
					if(n < 0 )
					{
						perror("NO HAY RECIBO DE CARACTERISTICAS DERESPUESTA");
					}else
					{
 						std::cout << "CARACTERISTICAS DE RESPUESTA RECIBIDA" << std::endl;
						//estado = 5;
						if(paquete.numeroMagico == NUMERO_MAGICO)
						{ 
							confirmacion = true;
							n = send(sock,&confirmacion,sizeof(bool),0);
							
 							std::cout << "-D- CHECKSUM: "<< paquete.checksum << " LARGO: " << paquete.largo << std::endl;
							
							if (n < 0)
							{
								perror("NO SE PUDO ENVIAR LA CONFIRMANCION DE  CARACTERISTICAS DE RESPUESTA (true)");
							}else
							{
								std::cout << "ENVIADA LA CONFIRMACION DE LA CARACTERISTICAS DE RESPUESTA (true)" << std::endl;
								estado = 5;
							}
						}else
						{
							confirmacion = false;
							n = send(sock,&confirmacion,sizeof(bool),0);
							
							if (n < 0)
							{
								perror("NO SE PUDO ENVIAR LA CONFIRMANCION DE  CARACTERISTICAS DE RESPUESTA (false)");
							}else
							{
								std::cout << "ENVIADA LA CONFIRMACION DE LA CARACTERISTICAS DE RESPUESTA (false)" << std::endl;
							}
						}
					}
			break;
			
			case 5:
				  respuesta = new byte[paquete.largo];
				  std::cout << "ESPERANDO DATOS" << std::endl;
					
					FD_ZERO(&readfds);
					FD_SET(sock, &readfds);
					select(sock+1, &readfds, NULL, NULL, &tv); 
					
					if (FD_ISSET(sock, &readfds))
					{
						n = recvfrom(sock,respuesta,paquete.largo,0,(struct sockaddr *)&server,&fromlen);
						
						
						std::cout << "-D- RESPUESTA DEL SERVIDOR \n" << respuesta << std::endl;
						
						md5(respuesta,paquete.largo,checksum);
						if(strcmp(paquete.checksum,checksum)==0)
						{
 							std::cout << "-D- CHECKSUM IGUALES" << std::endl;
							confirmacion = true;
							n = send(sock,&confirmacion,sizeof(bool),0);
							
							if (n < 0)
							{
								perror("PKT CONFIRMACION NO ENVIADO (true)");
							}else
							{
								std::cout << "PKT CONFIRMACION ENVIADO (true)" << std::endl;
								estado = 7;
							}
						}else
						{
 							std::cout << "-D- CHECKSUM DISTINTOS" << std::endl;
							confirmacion = false;
							n = send(sock,&confirmacion,sizeof(bool),0);
							if (n < 0)
							{
								perror("PKT CONFIRMACION NO ENVIADO (false)");
							}else
							{
								std::cout << "PKT CONFIRMACION ENVIADO (false)" << std::endl;
								estado = 4;;
							}
							
						}
						
					}else
					{
						std::cout << "TIME OUT " << std::endl;
						confirmacion = false;
						n = send(sock,&confirmacion,sizeof(bool),0);
						
						if (n < 0)
						{
							perror("PKT CONFIRMACION NO ENVIADO (false)");
						}else
						{
							std::cout << "PKT CONFIRMACION ENVIADO(false)" << std::endl;
							estado = 4;
						}
					}

			break;
			
			case 7:
			  
				  std::cin >> funcion;
				  md5(funcion,paquete.checksum);
				  paquete.largo=funcion.size();
				  paquete.checksum[33]='\0';
				  paquete.numeroMagico = NUMERO_MAGICO;
				  
				  estado = 0;
				  
			  break;
	
			default:
				  std::cout << "-D- CORRUPCION EN LA APLICACION VUELVA A EJECUTARLA." << std::endl;
				  return 0;
				break;
		}
	}
}